package test.threads.queue.unstressed.impl;

import java.lang.reflect.Field;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;

import com.google.common.base.Throwables;
import sun.misc.Unsafe;
import test.helpers.UnsafeHelper;
import test.threads.queue.unstressed.IBoundedQueueFactory;
import test.threads.queue.unstressed.IQueue;

/**
 * from A.S.
 * <p/>
 * Queue implementation:
 * Circular array based
 * Concurrent (single-enq, single-deq)
 * Bounded
 * Blocking (by spin-wait)
 * <p/>
 * <p/>
 * "Развернутая" версия {@link CABSESDMaskedQueue} -- без наследования, и прочих
 * изысков, которые, предположительно, могут смутить JIT, и уменьшить степень
 * оптимизации
 *
 * @author A.S.
 * @since 13.10.11,  15:06
 */
public final class CABSESDMaskedUnrolledQueue<T> implements IQueue<T> {
    private static final boolean USE_TEST_AND_SET = true;
    private static final boolean USE_UNSAFE = false;

    private static final int PADDING_SIZE = 0;//bytes
    private static final int PADDING_SIZE_IN_REFS = PADDING_SIZE / 4;//assume reference=4bytes


    public volatile long $i01, $i02, $i03, $i04, $i05, $i06, $i07, $i08;
    public volatile long $j01, $j02, $j03, $j04, $j05, $j06, $j07, $j08;

    private final T[] elements;
    private final int mask;

    public volatile long $k01, $k02, $k03, $k04, $k05, $k06, $k07, $k08;
    public volatile long $l01, $l02, $l03, $l04, $l05, $l06, $l07, $l08;

    private final int length;

    public volatile long $a01, $a02, $a03, $a04, $a05, $a06, $a07, $a08;
    public volatile long $b01, $b02, $b03, $b04, $b05, $b06, $b07, $b08;
    public volatile long headCursor = 0;
    public volatile long $c01, $c02, $c03, $c04, $c05, $c06, $c07, $c08;
    public volatile long $d01, $d02, $d03, $d04, $d05, $d06, $d07, $d08;

    public volatile long $e01, $e02, $e03, $e04, $e05, $e06, $e07, $e08;
    public volatile long $f01, $f02, $f03, $f04, $f05, $f06, $f07, $f08;
    public volatile long tailCursor = 0;
    public volatile long $g01, $g02, $g03, $g04, $g05, $g06, $g07, $g08;
    public volatile long $h01, $h02, $h03, $h04, $h05, $h06, $h07, $h08;

    private static final AtomicLongFieldUpdater<CABSESDMaskedUnrolledQueue> tailUpdater = AtomicLongFieldUpdater.newUpdater(
            CABSESDMaskedUnrolledQueue.class,
            "tailCursor"
    );
    private static final AtomicLongFieldUpdater<CABSESDMaskedUnrolledQueue> headUpdater = AtomicLongFieldUpdater.newUpdater(
            CABSESDMaskedUnrolledQueue.class,
            "headCursor"
    );

    private static final Unsafe unsafe;
    private static final long headCursorOffset;
    private static final long tailCursorOffset;

    static {
        unsafe = UnsafeHelper.unsafe();
        final Class<CABSESDMaskedUnrolledQueue> clazz = CABSESDMaskedUnrolledQueue.class;
        try {
            final Field headCursorField = clazz.getDeclaredField( "headCursor" );
            final Field tailCursorField = clazz.getDeclaredField( "tailCursor" );
            headCursorOffset = unsafe.objectFieldOffset( headCursorField );
            tailCursorOffset = unsafe.objectFieldOffset( tailCursorField );
        } catch ( NoSuchFieldException e ) {
            throw new RuntimeException( e );
        }
        System.out.printf(
                "%s: useUnsafe: %b, useTAS: %b\n",
                CABSESDMaskedUnrolledQueue.class.getSimpleName(),
                USE_UNSAFE,
                USE_TEST_AND_SET
        );
    }

    @SuppressWarnings( "unchecked" )
    public CABSESDMaskedUnrolledQueue( final int size ) {
        if ( Integer.bitCount( size ) != 1 ) {
            throw new IllegalArgumentException( "bufferSize must be a power of 2" );
        }

        length = size;
        mask = size - 1;
        elements = ( T[] ) new Object[size + 2 * PADDING_SIZE_IN_REFS];

        preventPaddingElimination();
    }

    public void enqueue( final T item ) {
        final long tail = tailCursor;

        final long target = tail - length;
        while ( target == headCursor ) {

        }

        final int index = ( int ) ( tail & mask ) + PADDING_SIZE_IN_REFS;
        if ( USE_TEST_AND_SET ) {
            if ( elements[index] != item ) {
                elements[index] = item;
            }
        } else {
            elements[index] = item;
        }

        if ( USE_UNSAFE ) {
            unsafe.putOrderedLong( this, tailCursorOffset, tail + 1 );
        } else {
            tailUpdater.lazySet( this, tail + 1 );
        }
    }

    public T dequeue() {
        final long head = headCursor;
        while ( head == tailCursor ) {

        }

        final int index = ( int ) ( head & mask ) + PADDING_SIZE_IN_REFS;
        final T item = elements[index];

        if ( USE_UNSAFE ) {
            unsafe.putOrderedLong( this, headCursorOffset, head + 1 );
        } else {
            headUpdater.lazySet( this, head + 1 );
        }
        return item;
    }

    @Override
    public int size() {
        return ( int ) ( tailCursor - headCursor );
    }

    public long preventPaddingElimination() {
        try {
            final Field[] fields = this.getClass().getFields();
            long sum = 0;
            for ( final Field field : fields ) {
                if ( field.getName().startsWith( "$" ) ) {
                    sum += ( ( Number ) field.get( this ) ).longValue();
                }
            }
            return sum;
        } catch ( IllegalAccessException e ) {
            throw Throwables.propagate( e );
        }
    }

    @SuppressWarnings( "unchecked" )
    public static <T> IBoundedQueueFactory<T, CABSESDMaskedQueue<T>> factory() {
        return FACTORY;
    }

    public static final IBoundedQueueFactory FACTORY = new IBoundedQueueFactory<Object, CABSESDMaskedUnrolledQueue<Object>>() {
        @Override
        public CABSESDMaskedUnrolledQueue<Object> create( final int size ) {
            return new CABSESDMaskedUnrolledQueue<Object>( size );
        }

        @Override
        public String toString() {
            return "CABSESDMaskedQueueUnrolledFactory[TAS=" + USE_TEST_AND_SET + "][unsafe=" + USE_UNSAFE + "]";
        }
    };

}
